package cn.yx.common.core.util.image;

import cn.yx.common.core.constant.message.image.ImageCompressUtilMessageKey;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.util.image.gif.AnimatedGifEncoder;
import cn.yx.common.core.util.image.gif.GifDecoder;
import lombok.extern.slf4j.Slf4j;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;

/**
 * <p>图片压缩工具类</p>
 *
 * @author Wgssmart
 */
@Slf4j
public final class ImageCompressUtil {

    private ImageCompressUtil() {
    }

    /**
     * 把图片按照指定的比例进行压缩，减小到指定的大小
     *
     * @param bufferedImage 原图BufferedImage
     * @param scale         压缩比例，1表示原图，0.5表示一半大小
     * @return
     */
    public static BufferedImage compress(BufferedImage bufferedImage, float scale) {
        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
        int newWidth = (int) (width * scale);
        int newHeight = (int) (height * scale);
        return newBufferedImage(bufferedImage, newWidth, newHeight);
    }

    /**
     * 把图片按照指定的大小进行压缩，保持纵横比，缩小时，按最小比例缩放，放大时，按最大比例放大
     *
     * @param bufferedImage 原图BufferedImage
     * @param newWidth      新宽度
     * @param newHeight     新高度
     * @param keepScale     是否保持纵横比，不指定纵横比的情况下，图片会严重失真
     * @return
     */
    public static BufferedImage compress(BufferedImage bufferedImage, int newWidth, int newHeight, boolean keepScale) {
        if (!keepScale) {
            // 如果不需要保持纵横比，直接按指定宽高返回新图片
            return newBufferedImage(bufferedImage, newWidth, newHeight);
        }
        // 保持纵横比返回新图片
        return compress(bufferedImage, newWidth, newHeight);
    }

    /**
     * 把图片按照指定的大小进行压缩，保持纵横比，缩小时，按最小比例缩放，放大时，按最大比例放大
     *
     * @param bufferedImage 原图BufferedImage
     * @param newWidth      新宽度
     * @param newHeight     新高度
     * @return
     */
    public static BufferedImage compress(BufferedImage bufferedImage, int newWidth, int newHeight) {
        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
        double widthScale = newWidth * 1.0 / width;
        double heightScale = newHeight * 1.0 / height;
        int toWidth = newWidth;
        int toHeight = newHeight;
        double scale;
        if (widthScale - 1.0 > 0 || heightScale - 1.0 > 0) {
            // 指定的宽或高有一个大于原始的值，则认为是放大，按最大比例来放大
            if (widthScale - heightScale < 0) {
                // 高不变，按最大比例调整宽
                scale = heightScale;
                toWidth = (int) (newWidth * scale);
            } else {
                // 宽不变，按最大比例调整高
                scale = widthScale;
                toHeight = (int) (newHeight * scale);
            }
        }
        if (widthScale - 1.0 < 0 && heightScale - 1.0 < 0) {
            // 指定的宽和高都小于原始的值，则认为是缩小，按最小比例来缩小
            if (widthScale - heightScale < 0) {
                // 宽不变，按最小比例调整高
                scale = widthScale;
                toHeight = (int) (newHeight * scale);
            } else {
                // 高不变，按最小比例调整宽
                scale = heightScale;
                toWidth = (int) (newWidth * scale);
            }
        }
        return newBufferedImage(bufferedImage, toWidth, toHeight);
    }

    /**
     * 以新的尺寸创建BufferedImage
     *
     * @param bufferedImage
     * @param newWidth
     * @param newHeight
     * @return
     */
    private static BufferedImage newBufferedImage(BufferedImage bufferedImage, int newWidth, int newHeight) {
        BufferedImage newImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
        Graphics graphics = newImage.getGraphics();
        graphics.drawImage(bufferedImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH), 0, 0, null);
        return newImage;
    }

    /**
     * 把图片按照指定的比例进行压缩，减小到指定的大小
     *
     * @param path  原图路径
     * @param scale 压缩比例，1表示原图，0.5表示一半大小
     * @return
     */
    public static BufferedImage compress(String path, float scale) {
        return compress(ImageUtil.bufferedImage(path), scale);
    }

    /**
     * 把图片按照指定的大小进行压缩，保持图片纵横比
     *
     * @param path      原图路径
     * @param newWidth  新宽度
     * @param newHeight 新高度
     * @return
     */
    public static BufferedImage compress(String path, int newWidth, int newHeight) {
        return compress(ImageUtil.bufferedImage(path), newWidth, newHeight);
    }

    /**
     * 把图片按照指定的比例进行压缩，减小到指定的大小
     *
     * @param inputStream 原图InputStream
     * @param scale       压缩比例，1表示原图，0.5表示一半大小
     * @return
     */
    public static BufferedImage compress(InputStream inputStream, float scale) {
        return compress(ImageUtil.bufferedImage(inputStream), scale);
    }

    /**
     * 把图片按照指定的大小进行压缩，保持图片纵横比
     *
     * @param inputStream 原图InputStream
     * @param newWidth    新宽度
     * @param newHeight   新高度
     * @return
     */
    public static BufferedImage compress(InputStream inputStream, int newWidth, int newHeight) {
        return compress(ImageUtil.bufferedImage(inputStream), newWidth, newHeight);
    }

    /**
     * 压缩gif图片到指定比例，并输出gif图片到指定的输出流
     *
     * @param path         gif图片路径
     * @param scale        压缩比例，1表示原图，0.5表示一半大小
     * @param outputStream 输出流，可以是FileOutputStream，也可以是response.getOutputStream
     */
    public static void compressGif(String path, float scale, OutputStream outputStream) {
        writeGifToOutputStream(getGifDecoder(path), scale, outputStream);
    }

    /**
     * 压缩gif图片到指定大小，并输出gif图片到指定的输出流，保持图片纵横比
     *
     * @param path         gif图片路径
     * @param newWidth     新宽度
     * @param newHeight    新高度
     * @param outputStream 输出流，可以是FileOutputStream，也可以是response.getOutputStream
     */
    public static void compressGif(String path, int newWidth, int newHeight, OutputStream outputStream) {
        writeGifToOutputStream(getGifDecoder(path), newWidth, newHeight, outputStream);
    }

    /**
     * 压缩gif图片到指定比例，并输出gif图片到指定的输出流
     *
     * @param inputStream  gif图片输入流
     * @param scale        压缩比例，1表示原图，0.5表示一半大小
     * @param outputStream 输出流，可以是FileOutputStream，也可以是response.getOutputStream
     */
    public static void compressGif(InputStream inputStream, float scale, OutputStream outputStream) {
        writeGifToOutputStream(getGifDecoder(inputStream), scale, outputStream);
    }

    /**
     * 压缩gif图片到指定大小，并输出gif图片到指定的输出流，保持图片纵横比
     *
     * @param inputStream  gif图片输入流
     * @param newWidth     新宽度
     * @param newHeight    新高度
     * @param outputStream 输出流，可以是FileOutputStream，也可以是response.getOutputStream
     */
    public static void compressGif(InputStream inputStream, int newWidth, int newHeight, OutputStream outputStream) {
        GifDecoder gifDecoder = getGifDecoder(inputStream);
        if (Objects.isNull(gifDecoder)) {
            // @SimplifiedChinese=Gif图片解析异常
            // @English=Gif decoder error
            throw new CommonException(ImageCompressUtilMessageKey.GET_GIF_DECODER_ERROR);
        }
        writeGifToOutputStream(gifDecoder, newWidth, newHeight, outputStream);
    }

    /**
     * 读取GifDecoder
     *
     * @param path
     * @return
     */
    private static GifDecoder getGifDecoder(String path) {
        GifDecoder gifDecoder = new GifDecoder();
        int status = gifDecoder.read(path);
        if (status == GifDecoder.STATUS_OK) {
            return gifDecoder;
        }
        log.error("get gif decoder from path error, status: {}", status);
        return null;
    }

    /**
     * 读取GifDecoder
     *
     * @param inputStream
     * @return
     */
    private static GifDecoder getGifDecoder(InputStream inputStream) {
        GifDecoder gifDecoder = new GifDecoder();
        int status = gifDecoder.read(inputStream);
        if (status == GifDecoder.STATUS_OK) {
            return gifDecoder;
        }
        log.error("get gif decoder from input stream error, status: {}", status);
        return null;
    }

    /**
     * 把gif图片写出到指定的输出流
     *
     * @param gifDecoder
     * @param scale
     * @param outputStream
     */
    private static void writeGifToOutputStream(GifDecoder gifDecoder, float scale, OutputStream outputStream) {
        int frameCount = gifDecoder.getFrameCount();
        AnimatedGifEncoder animatedGifEncoder = new AnimatedGifEncoder();
        animatedGifEncoder.start(outputStream);
        animatedGifEncoder.setRepeat(gifDecoder.getLoopCount());
        for (int i = 0; i < frameCount; i++) {
            animatedGifEncoder.setDelay(gifDecoder.getDelay(i));
            BufferedImage frameImage = gifDecoder.getFrame(i);
            animatedGifEncoder.addFrame(compress(frameImage, scale));
        }
        animatedGifEncoder.finish();
    }

    /**
     * 把gif图片写出到指定的输出流，保持图片纵横比
     *
     * @param gifDecoder
     * @param newWidth     新宽度
     * @param newHeight    新高度
     * @param outputStream
     */
    private static void writeGifToOutputStream(GifDecoder gifDecoder, int newWidth, int newHeight, OutputStream outputStream) {
        int frameCount = gifDecoder.getFrameCount();
        AnimatedGifEncoder animatedGifEncoder = new AnimatedGifEncoder();
        animatedGifEncoder.start(outputStream);
        animatedGifEncoder.setRepeat(gifDecoder.getLoopCount());
        for (int i = 0; i < frameCount; i++) {
            animatedGifEncoder.setDelay(gifDecoder.getDelay(i));
            BufferedImage frameImage = gifDecoder.getFrame(i);
            animatedGifEncoder.addFrame(compress(frameImage, newWidth, newHeight));
        }
        animatedGifEncoder.finish();
    }

}
